package org.amse.korovkin.ciphering.model;
import org.amse.korovkin.ciphering.model.cipher.monocipher.ChangeTable;
import java.io.*;
import org.amse.korovkin.ciphering.Main;

public class BigramFrequency {


    private int myNum = 0;
    private char[] myLetter = new char[Main.NUM_LETTERS];
    private double[][] myFrequency = new double[Main.NUM_LETTERS][Main.NUM_LETTERS];
    /**
     * The maximum value of penalty to Table of Bigram
     * @see org.amse.miha.ciphering.BigramFrequency#compare(BigramFrequency standart, BigramFrequency current)
     */
    private static final double BI_TABLE_THRESHOLD = 0.4;
    /**
     * The maximum value of penalty to one Frequency in Table of Bigram
     * @see org.amse.miha.ciphering.BigramFrequency#compare(BigramFrequency standart, BigramFrequency current)
     */
    private static final double BIGRAM_THRESHOLD = 0.05;

    private int getNum() {
        return myNum;
    }
    private int getLetter(int index) {
        if (index < myNum) {
            return (int) myLetter[index];
        } else {
            return -1;
        }
    }
    private double getFrequency(int i, int j) {
        return myFrequency[i][j];
    }
   
    public void divFrequency(int divizor) {
        for (int i = 0; i < myNum; i++) {
            for (int j = 0; j < myNum; j++) {            
                myFrequency[i][j] /= divizor;
            }    
        }
    }
    public void incFrequency(char ch1, char ch2, int additive) {
        for (int i = 0; i < myNum; i++) {
            if (myLetter[i] == ch1) {
                for (int j = 0; j < myNum; j++) {
                    if (myLetter[j] == ch2) {
                        myFrequency[i][j] += additive;
                        return;
                    }
                }
            }
        }
    }
    public void setFrequency(double frequency, char ch1, char ch2) {
        for (int i = 0; i < myNum; i++) {
            if (myLetter[i] == ch1) {
                for (int j = 0; j < myNum; j++) {
                    if (myLetter[j] == ch2) {
                        myFrequency[i][j] = frequency;
                        return;
                    }
                }
            }
        }
    }

    public void clone(BigramFrequency bf) {
        myNum = bf.getNum();
        for (int i = 0; i < myNum; i++) {
            myLetter[i] = (char) bf.getLetter(i);
            for (int j = 0; j < myNum; j++) {
                myFrequency[i][j] = bf.getFrequency(i, j);
            }
        }
    }
    /**
     *
     * @param ch
     * @return
     */
    public boolean contains(char ch) {       
        ch = ChangeTable.toLowerCase(ch);
        for (int i = 0; i < myNum; i++) {
            if (myLetter[i] == ch) {
                return true;
            }            
        }
        return false;
    }
    public void addChar(char ch) {
        ch = ChangeTable.toLowerCase(ch);
        if (!this.contains(ch)) {
            myLetter[myNum] = ch;
            myNum++;
        }
    }
    
    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        for (int i = 0; i < myNum; i++) {
            res.append(myLetter[i]);
            for (int j = 0; j < myNum; j++) {
                res.append(" *" + (char) this.getLetter(j) + "*" + myFrequency[i][j]);
            }
            res.append("\n");
        }
        return res.toString();
    }

    public void sortLetters() {
        for (int i = 0; i < myNum; i++) {
            for (int j = i; j < myNum; j++) {
                if (myLetter[i] > myLetter[j]) {
                    char buf = myLetter[i];
                    myLetter[i] = myLetter[j];
                    myLetter[j] = buf;
                    double buffer;
                    for (int k = 0; k < myNum; k++) {
                        buffer = myFrequency[i][k];
                        myFrequency[i][k] = myFrequency[j][k];
                        myFrequency[j][k] = buffer;
                    }
                    for (int k = 0; k < myNum; k++) {
                        buffer = myFrequency[k][i];
                        myFrequency[k][i] = myFrequency[k][j];
                        myFrequency[k][j] = buffer;
                    }
                }
            }
        }
    }

    /**
     * Transpose the BigramTable by ChangeTable
     * @param bigram BigramTable that must be transpose
     * @param table ChangeTable for transposing
     */
    public void transpose(BigramFrequency bigram, ChangeTable table) {
        myNum = bigram.getNum();
        for (int i = 0; i < myNum; i++) {
            myLetter[i] = (char) bigram.getLetter(i);
        }
        for (int i = 0; i < myNum; i++) {
            for (int j = 0; j < myNum; j++) {
                char ch1 = (char)table.getLetter1((char)bigram.getLetter(i));
                char ch2 = (char)table.getLetter1((char)bigram.getLetter(j));                
                this.setFrequency(bigram.getFrequency(i, j), ch1, ch2);
            }
            
        }
    }

    public void writeToFile(DataOutputStream stream)
    throws IOException {
        stream.writeInt(myNum);
        for (int i = 0; i < myNum; i++) {
            stream.writeChar(myLetter[i]);
        }
        for (int i = 0; i < myNum; i++) {
            for (int j = 0; j < myNum; j++) {
                stream.writeDouble(myFrequency[i][j]);
            }
        }
    }
    public void readFromFile(String inputFile)
        throws IOException {
        DataInputStream dis = new DataInputStream(new FileInputStream(inputFile));
        // skip the TableOfFrequency in the begin of statistic file
        int n = dis.readInt();
        for (int i = 0; i < n; i++) {
            dis.readChar();
            dis.readDouble();
        }
        n = dis.readInt();
        System.out.println(n);
        for (int i = 0; i < n; i++) {
            this.addChar(dis.readChar());
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                this.setFrequency(dis.readDouble(),
                (char)this.getLetter(i), (char)this.getLetter(j));
            }
        }
        dis.close();
    }
    /**
     * Comparing two BigramTable. One of them is Standart and other is compared with standart.
     * If Bigram Frequency in two tables are not equal compound a penalty.
     * If penalty is very big return -1.
     * @param standart Standart BigramTable
     * @param current Compared BigramTable
     * @return Penalty - difference between two tables.
     */
    public static double compare(BigramFrequency standart, BigramFrequency current) {
        double penalty = 0;
        for (int i = 0; i < standart.getNum(); i++) {
            for (int j = 0; j < standart.getNum(); j++) {
                if (PossibilityTable.ALPHABET.contains((char)standart.getLetter(i))
                        && PossibilityTable.ALPHABET.contains((char)standart.getLetter(j))) {
                    double x = Math.abs(standart.getFrequency(i, j) 
                            - current.findFrequency((char)standart.getLetter(i), (char)standart.getLetter(j)));
                    if (x > BIGRAM_THRESHOLD) {
                        return -1;
                    } else {
                        penalty += x;
                    }
                }
            }
            int asd = 0;
            if (penalty > BI_TABLE_THRESHOLD
                    || penalty > Analyzator.minBiPenalty) {
                return -1;
            }
        }
        return penalty;
    }
    private double findFrequency(char ch1, char ch2) {
        for (int i = 0; i < myNum; i++) {
            if (myLetter[i] == ch1) {
                for (int j = 0; j < myNum; j++) {
                    if (myLetter[j] == ch2) {
                        return myFrequency[i][j];
                    }
                }
            }
        }
        return 0;
    }

}
